package vip.liux.front.infrastructure.utils;

import org.apache.commons.lang3.tuple.Pair;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class AmbientScopeProvider<T> {
    private final Map<String, ScopeItem> scopeMap = new ConcurrentHashMap<>();
    private final AmbientDataContext dataContext;

    public AmbientScopeProvider(AmbientDataContext dataContext) {
        this.dataContext = dataContext;
    }

    public T getValue(String contextKey) {
        ScopeItem item = getCurrentItem(contextKey);
        if (item == null) {
            return null;
        }
        return item.value;
    }

    public AutoCloseable beginScope(String contextKey, T value) {
        ScopeItem item = new ScopeItem(value, getCurrentItem(contextKey));

        if (!(scopeMap.putIfAbsent(item.id, item) == null)) {
            throw new RuntimeException("Can not add item! ScopeDictionary.TryAdd returns false!");
        }

        dataContext.set(contextKey, item.id);

        return new CloseableAction<>(state -> {

            Map<String, ScopeItem> scopeMap = state.getLeft().getLeft();
            ScopeItem scopeItem = state.getLeft().getRight();
            AmbientDataContext dataContext = state.getRight().getLeft();
            String key = state.getRight().getRight();

            scopeMap.remove(scopeItem.id);

            if (scopeItem.outer == null) {
                dataContext.set(scopeItem.id, null);
                return;
            }
            dataContext.set(key, scopeItem.outer.id);
        }, Pair.of(Pair.of(scopeMap, item), Pair.of(dataContext, contextKey)));
    }

    private ScopeItem getCurrentItem(String contextKey) {
        return dataContext.get(contextKey) instanceof String objKey ? scopeMap.getOrDefault(objKey, null) : null;
    }

    protected class ScopeItem {

        private final String id;

        private final ScopeItem outer;

        private final T value;

        public ScopeItem(T value, ScopeItem outer) {
            this.id = java.util.UUID.randomUUID().toString();

            this.outer = outer;
            this.value = value;
        }
    }
}
